/*
 * 作者: junmu
 * 时间: 2025-07-22
 */
package com.hzbc.faceattendance.utils;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.time.Duration;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Component
public class JwtUtil {

    private final SecretKey secretKey;
    private final Long expiration;
    @Value("${jwt.clockSkew}")
    private  Integer clockSkew; // 允许的时钟偏移时间(秒)

    public JwtUtil(SecretKey secretKey, @Value("${jwt.expiration}") Long expiration) {
        this.secretKey = secretKey;
        this.expiration = expiration;
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails.getUsername());
    }
    /**
     * 创建JWT令牌(私有方法)
     * @param claims 声明信息
     * @param subject 主题(通常为用户名)
     * @return 生成的JWT令牌字符串
     */
    // ... existing code ...
    private String createToken(Map<String, Object> claims, String subject) {
        // 使用中国时区计算时间
        ZoneId zone = ZoneId.of("Asia/Shanghai");
        ZonedDateTime now = ZonedDateTime.now(zone);

        // 计算过期时间（当前时间 + 有效期时长）
        ZonedDateTime expirationTime = now.plus(Duration.ofMillis(expiration));

        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(Date.from(now.toInstant()))
                .setExpiration(Date.from(expirationTime.toInstant()))
                .signWith(secretKey, SignatureAlgorithm.HS256)
                .compact();
    }

//    private Claims extractAllClaims(String token) {
//        return Jwts.parserBuilder()
//                .setSigningKey(secretKey)
//                .setAllowedClockSkewSeconds(clockSkew) // 添加时钟偏移
//                .build()
//                .parseClaimsJws(token)
//                .getBody();
//    }
// ... existing code ...
    /**
     * 验证JWT令牌是否有效
     * @param token JWT令牌
     * @param userDetails 用户详细信息
     * @return 如果令牌有效且未过期返回true，否则返回false
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        try {
            final String username = extractUsername(token);
            return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
        } catch (Exception e) {
            return false;
        }
    }
    public boolean canRefreshToken(String token) {
        try {
            // 即使过期也提取声明
            final Claims claims = extractAllClaims(token);
            // 检查过期时间是否在允许的时钟偏移范围内
            return !claims.getExpiration().before(new Date(System.currentTimeMillis() - clockSkew * 1000));
        } catch (Exception e) {
            return false;
        }
    }
    /**
     * 从令牌中提取用户名
     * @param token JWT令牌
     * @return 用户名
     */
    public String extractUsername(String token) throws BadCredentialsException {
        return extractClaim(token, Claims::getSubject);
    }

    public Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

    private <T> T extractClaim(String token, Function<Claims, T> claimsResolver) throws BadCredentialsException {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

    private Claims extractAllClaims(String token) throws BadCredentialsException {
        return Jwts.parserBuilder()
                .setSigningKey(secretKey)
                .setAllowedClockSkewSeconds(clockSkew) // 启用时钟偏移配置
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    /**
     * 检查令牌是否已过期
     * @param token JWT令牌
     * @return 如果已过期返回true，否则返回false
     */
    private Boolean isTokenExpired(String token) {
        try {
            Date expiration = extractExpiration(token);
            ZonedDateTime now = ZonedDateTime.now(ZoneId.of("Asia/Shanghai"));
            ZonedDateTime expirationZdt = expiration.toInstant().atZone(ZoneId.of("Asia/Shanghai"));
            return expirationZdt.isBefore(now.minusSeconds(clockSkew));
        } catch (Exception e) {
            return true;
        }
    }

    public String generateRefreshToken(UserDetails userDetails, long freeExpirationMs) {
        // 使用中国时区计算时间
        ZoneId zone = ZoneId.of("Asia/Shanghai");
        ZonedDateTime now = ZonedDateTime.now(zone);

        // 计算过期时间（当前时间 + 有效期时长）
        ZonedDateTime expirationTime = now.plus(Duration.ofMillis(expiration));
        ZonedDateTime refreshExpirationTime = expirationTime.plus(Duration.ofMillis(freeExpirationMs));
        Map<String, Object> claims = new HashMap<>();
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(Date.from(now.toInstant()))
                .setExpiration(Date.from(refreshExpirationTime.toInstant()))
                .signWith(secretKey, SignatureAlgorithm.HS256)
                .compact();
    }

}